/**
 * Copyright 2011 Christian Padovano
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package it.sijom;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import it.sijom.annotations.target;
import it.sijom.exceptions.DataMappingException;

/**
 * Implements the attribute conversion between objects using custom annotations to mark it.
 * @author christian padovano 
 * @version 1.0
 */
public abstract class AJavaObjectMapperConverter extends AJavaObjectMapper {
	
	private Logger log = Logger.getLogger(AJavaObjectMapperConverter.class);

	@Override
	public Object doObjectMappingNoCaseSensitive(Object source,
			Class targetClass) throws DataMappingException 
	{
		//check if 'target' annotation are declared on the attributes' object
		if (getTargetAnnotations(source)!=null) {
			return convert(source, targetClass);
		}
		//otherwise ... call the super implementation..
		return super.doObjectMappingNoCaseSensitive(source, targetClass);
	}
	
	
	/**
	 * Coopy object attributes when source and target attributes name doesn't match.
	 * @param source source object model
	 * @param targetClass Target class object model
	 * @return instance of the target class.
	 */
	protected Object convert(Object source, Class targetClass) throws DataMappingException {
		Object result = null;
		try {
			result = targetClass.newInstance();
			Map<String,String> targetAttributes = new HashMap<String,String>();
			Method[] sourceMethods = getSource2TargetMapping(source,targetAttributes);
			
			int a=0;
			//begin to copy all attributes marked with 'target'annotation ....
			for (a = 0; a < sourceMethods.length; a++) {
				String attributeName=sourceMethods[a].getName().toLowerCase(); 
				if (attributeName.startsWith("get")) {		
						String methodName = targetAttributes.get(attributeName.substring(3));
						//retrieve the source object attributeName value
						Object value = getProperty(source, attributeName.substring(3));
						try {
						  //use the adapter to copy the source attribute value in the target object attribute	
						  dataAdapter.copy(value, result, methodName);
						} catch (Exception e) {
							if (!this.silentMode) {
								try {
									throw new Exception(e);
								} catch (Exception e1) {											
									log.warn(e1);
								}
							}
							log.warn("   Attribute not found '"+methodName+"' in " + result);
						} 
					
				} else if (attributeName.startsWith("is")) { //if a field start with "is" it convert the primitive type 'boolean' in the relative object Boolean
					Object dummy = getProperty(source, attributeName.substring(2));
					if (dummy!=null) {
					  BeanUtils.setProperty(result, attributeName.substring(2), dummy);
					} else {
						log.warn("No property found with attribute " + attributeName + " on object ["+source.getClass().getName()+"]");
					}
				}
			}
		} catch (InstantiationException e) {
			if (!this.silentMode) 
			   throw new DataMappingException(e);
		} catch (IllegalAccessException e) {
			if (!this.silentMode) 
				   throw new DataMappingException(e);
		} catch (InvocationTargetException e) {
			if (!this.silentMode) 
				   throw new DataMappingException(e);
		}
		return result;
		
	}
	
	/**
	 * Return the attributes name annotated with 'target' annotation
	 * @param source instance of the object
	 * @param targetAttributes fill this map with the target attributes as specified from the 'target' annotation
	 * @return Return the attributes name annotated with 'target' annotation
	 */
	protected Method[] getSource2TargetMapping(Object source, Map<String,String> targetAttributes) {
		List<Method> attributeList = new ArrayList<Method>();
		Field[] fields = getTargetAnnotations(source);
		
		
		for (int i = 0; i < fields.length; i++) {
			StringBuffer buffer = new StringBuffer();
			Method _m = null;
			buffer.append("get").append(StringUtils.capitalize(fields[i].getName()));
			try {
			  _m = source.getClass().getMethod(buffer.toString());
			  attributeList.add(_m);
			} catch (Exception e) {
				try {
					   StringBuffer buffer2 = new StringBuffer();
					   buffer2.append("is").append(StringUtils.capitalize(fields[i].getName()));
					  _m = source.getClass().getMethod(buffer2.toString());
					  attributeList.add(_m);
				} catch (Exception x) {
						_m = null;
				} 
			} 
			
			if ( _m!=null) {
				if (fields[i].isAnnotationPresent(target.class)) {
					targetAttributes.put(fields[i].getName().toLowerCase(), ((target)fields[i].getAnnotation(target.class)).targetField());
				} else {
					targetAttributes.put(fields[i].getName().toLowerCase(), fields[i].getName());
				}
			}
		}
		return attributeList.toArray(new Method[0]);
	}


   /**
    * Detect if a 'target' annotation type is present on the source object	
    * @param source instance of the source object
    * @return array of field or 'null' for no annotations declared
    */
   protected Field[] getTargetAnnotations(Object source) {
	   Field[] fieldList = source.getClass().getDeclaredFields();
	   for (int i = 0; i < fieldList.length; i++) {
		 if (fieldList[i].isAnnotationPresent(target.class)) {
			 return fieldList; 
		 }
	   }
	   return null;
   }
	
	

}
